package org.zoomdev.zoom.aop.utils;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.Method;

public class ClassBuilder {

    private static final Log logger = LogFactory.getLog(ClassBuilder.class);
    private final ClassLoader classLoader;
    private final ClassPool classPool;
    private final String className;
    private CtClass subClass;

    public ClassBuilder(ClassLoader loader, ClassPool pool, String className) {

        this.classLoader = loader;
        this.classPool = pool;
        this.className = className;
    }

    private static String getMethodDesc(Method method, String methodPrefix) {

        StringBuilder sb = new StringBuilder();
        Class<?> returnType = method.getReturnType();
        sb.append(class2Type(returnType));
        sb.append(" ");
        if (methodPrefix != null) {
            sb.append(methodPrefix);
        }
        sb.append(method.getName());
        sb.append("(");
        parseParamTypes(method, sb);
        sb.append(")");

        return sb.toString();
    }

    // 比如  login(String p0,String password p1)
    private static void parseParamTypes(Method method, StringBuilder sb) {
        boolean first = true;
        int index = 0;
        for (Class<?> type : method.getParameterTypes()) {
            if (first) {
                first = false;
            } else {
                sb.append(",");
            }
            String name = type.getName();
            if (type.isArray()) {
                name = type.getComponentType().getName() + "[]";
            }


            sb.append(name);
            sb.append(" p");
            sb.append(index++);
        }
    }

    private static String class2Type(Class<?> clazz) {
        if (clazz == Void.class) {
            return "void";
        }
        if (clazz.isArray()) {

            return clazz.getCanonicalName();
        }
        return clazz.getName();
    }

    private static CtMethod toPublic(CtMethod method) {
        method.setModifiers(Modifier.PUBLIC);
        return method;
    }

    public void createClass() {
        subClass = classPool.makeClass(className);
    }

    public void findClass() throws NotFoundException {
        subClass = classPool.get(className);
    }

    public void addConstructor(Class[] types, String body) throws CannotCompileException, NotFoundException {

        CtClass[] classes = new CtClass[types.length];
        for (int i = 0; i < types.length; ++i) {
            classes[i] = classPool.get(types[i].getName());
        }
        CtConstructor cons1 = new CtConstructor(classes, subClass);
        cons1.setBody("{" + body + "}");
        cons1.setModifiers(Modifier.PUBLIC);
        subClass.addConstructor(cons1);
    }

    public void addInterface(Class type) throws NotFoundException {
        CtClass interfaceClass = findClass(type);
        subClass.addInterface(interfaceClass);
    }

    public void setSuperClass(Class<?> type) throws NotFoundException, CannotCompileException {
        CtClass superClass = findClass(type);
        subClass.setModifiers(Modifier.PUBLIC);
        subClass.setSuperclass(superClass);
    }

    protected CtClass findClass(Class type) throws NotFoundException {
        try {
            return classPool.get(type.getName());
        } catch (NotFoundException e) {
            classPool.appendClassPath(new ClassClassPath(type));
            return classPool.get(type.getName());
        }
    }

    public Class toClass() throws CannotCompileException, NotFoundException {
        return subClass.toClass(classLoader, null);
    }

    public void addField(Class type, String name, int modifier) throws NotFoundException, CannotCompileException {
        if (logger.isDebugEnabled()) {
            logger.debug("add field :" + name + " type: " + type);
        }
        CtField field = new CtField(classPool.get(type.getName()),
                name, subClass);
        field.setModifiers(modifier);
        subClass.addField(field);
    }


    public void addMethod(Method method, String body) throws CannotCompileException {
        addMethod(method, null, body);
    }

    public void addMethod(Method method, String methodPrefix, String body) throws CannotCompileException {
        subClass.addMethod(toPublic(createMethod(method, methodPrefix, body)));
    }

    public void addMethod(String methodName, Class returnType, String body) throws CannotCompileException {
        subClass.addMethod(toPublic(createMethod(methodName, returnType, body)));
    }

    private CtMethod createMethod(String methodName, Class returnType, String body) throws CannotCompileException {
        String str = String.format("%s %s(){%s}", class2Type(returnType), methodName, body);

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("add method:%s", str));
        }


        return CtMethod.make(str, subClass);

    }


    private CtMethod createMethod(Method method, String methodPrefix, String body) throws CannotCompileException {
        StringBuilder sb = new StringBuilder();
        sb.append(getMethodDesc(method, methodPrefix));
        sb.append("{");
        sb.append(body);
        sb.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("add method:%s", sb));
        }


        return CtMethod.make(sb.toString(), subClass);

    }


}
